# (C) 2019-2020 lifegpc
# This file is part of bili.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
from lang import getdict, getlan
from command import gopt
import sys
from JSONParser import loadset
from typing import List
from re import search, I


class ASSScriptInfo():
    """存储[Script Info]部分的信息"""
    Title: str = None
    __ScriptType = 'v4.00+'
    WrapStyle: int = None
    ScaledBorderAndShadow: bool = None
    YCbCr_Matrix: str = None
    PlayResX: int = None
    PlayResY: int = None
    __Comment: List[str] = None

    def __init__(self):
        self.Title = ''
        self.WrapStyle = 0
        self.ScaledBorderAndShadow = True
        self.YCbCr_Matrix = 'None'
        self.PlayResX = 1920
        self.PlayResY = 1080
        self.__Comment: List[str] = []

    def addComment(self, s: str):
        "添加评论"
        if isinstance(s, str):
            self.__Comment.append(s)
        else:
            raise ValueError(lan['COM_MUST_STR'])  # 评论必须是字符串。

    def removeCommentByIndex(self, i: int):
        "通过索引删除评论"
        if i < len(self.__Comment):
            del self.__Comment[i]
        else:
            raise IndexError(i)

    def removeCommentByContent(self, s: str):
        "通过内容删除评论"
        if isinstance(s, str):
            self.__Comment.remove(s)
        else:
            raise ValueError(lan['COM_MUST_STR'])

    def dump(self) -> str:
        "生成[Script Info]部分字符串"
        s = """[Script Info]
; Script generated by bili ASSWriter.
; https://github.com/lifegpc/bili\n"""
        if len(self.__Comment) > 0:
            comment = []
            for i in self.__Comment:
                l = i.split('\n')
                for j in l:
                    comment.append(j)
            for i in comment:
                s = f"{s}; {i}\n"
        sb = 'yes' if self.ScaledBorderAndShadow else 'no'
        px = self.PlayResX if self.PlayResX > 0 else 1920
        py = self.PlayResY if self.PlayResY > 0 else 1080
        s = f"""{s}Title: {self.Title}
ScriptType: {self.__ScriptType}
WrapStyle: {self.WrapStyle}
ScaledBorderAndShadow: {sb}
YCbCr Matrix: {self.YCbCr_Matrix}
PlayResX: {px}
PlayResY: {py}\n"""
        return s


class ASSScriptColor():
    """ASS脚本颜色"""
    __red = 0
    __green = 0
    __blue = 0
    __alpha = 0

    def __init__(self, red: int = 0, green: int = 0, blue: int = 0, alpha: int = 0):
        self.set_color(red, green, blue, alpha)

    def __check_color(self, color: int) -> bool:
        "检查颜色值是否合法"
        if color is None:
            return False
        elif not isinstance(color, int):
            raise ValueError(lan['COLOR_MUST_INT'])
        elif color < 0 or color > 255:
            raise ValueError(lan['COLOR_OUT_OF_RANGE'])
        else:
            return True

    def set_color(self, red: int = None, green: int = None, blue: int = None, alpha: int = None):
        if self.__check_color(red):
            self.__red = red
        if self.__check_color(green):
            self.__green = green
        if self.__check_color(blue):
            self.__blue = blue
        if self.__check_color(alpha):
            self.__alpha = alpha

    def get_red(self) -> int:
        return self.__red

    def get_green(self) -> int:
        return self.__green

    def get_blue(self) -> int:
        return self.__blue

    def get_alpha(self) -> int:
        return self.__alpha

    def dump(self) -> str:
        return "&H{:0>2X}{:0>2X}{:0>2X}{:0>2X}".format(self.__alpha, self.__blue, self.__green, self.__red)


class ASSScriptStyle():
    """ASS脚本字体样式"""
    Name: str = None
    Fontname: str = None
    __Fontsize: int = None
    PrimaryColour: ASSScriptColor = None
    SecondaryColour: ASSScriptColor = None
    OutlineColour: ASSScriptColor = None
    BackColour: ASSScriptColor = None
    __Bold: bool = None
    __Italic: bool = None
    __Underline: bool = None
    __StrikeOut: bool = None
    __ScaleX: int = None
    __ScaleY: int = None
    __Spacing: int = None
    __Angle: float = None
    __BorderStyle: bool = None  # False代表1 True代表3
    __Outline: int = None
    __Shadow: int = None
    __Alignment: int = None
    __MarginL: int = None
    __MarginR: int = None
    __MarginV: int = None
    __Encoding: int = None

    def __init__(self):
        self.Name = 'Default'
        self.Fontname = 'Arial'
        self.__Fontsize = 20
        self.PrimaryColour = ASSScriptColor(255, 255, 255)
        self.SecondaryColour = ASSScriptColor(255)
        self.OutlineColour = ASSScriptColor()
        self.BackColour = ASSScriptColor()
        self.__Bold = False
        self.__Italic = False
        self.__Underline = False
        self.__StrikeOut = False
        self.__ScaleX = 100
        self.__ScaleY = 100
        self.__Spacing = 0
        self.__Angle = 0.0
        self.__BorderStyle = False
        self.__Outline = 2
        self.__Shadow = 2
        self.__Alignment = 2
        self.__MarginL = 10
        self.__MarginR = 10
        self.__MarginV = 10
        self.__Encoding = 1

    def __check_int(self, i: int) -> bool:
        "检查是否为大于等于0的整数（浮点数会转换为整数）"
        if i is None:
            return False
        elif isinstance(i, int):
            if i >= 0:
                return True
            else:
                raise ValueError(lan['OUT_OF_RANGE'])
        elif isinstance(i, float):
            i = round(i)
            if i >= 0:
                return True
            else:
                raise ValueError(lan['OUT_OF_RANGE'])
        else:
            raise ValueError(lan['MUST_NUM'])

    def __check_float(self, i: float) -> bool:
        "检查是否为大于等于0的浮点数（整数会转换为浮点数）"
        if i is None:
            return False
        elif isinstance(i, float):
            if i >= 0:
                return True
            else:
                raise ValueError(lan['OUT_OF_RANGE'])
        elif isinstance(i, int):
            i = float(i)
            if i >= 0:
                return True
            else:
                raise ValueError(lan['OUT_OF_RANGE'])
        else:
            raise ValueError(lan['MUST_NUM'])

    def __check_bool(self, b: bool) -> bool:
        if b is None:
            return False
        elif isinstance(b, bool):
            return True
        else:
            raise ValueError(lan['MUST_BOOL'])

    def __check_align(self, i: int) -> bool:
        if i is None:
            return False
        elif isinstance(i, (int, float)):
            i = round(i)
            if i >= 1 and i <= 9:
                return True
            raise ValueError(lan['ALIGN_OUT_OF_RANGE'])
        raise ValueError(lan['MUST_NUM'])

    def get_Fontsize(self) -> int:
        return self.__Fontsize

    def set_Fontsize(self, i: int):
        if self.__check_int(i):
            if round(i) < 1:
                i = 1
            self.__Fontsize = round(i)

    def get_Bold(self) -> bool:
        return self.__Bold

    def set_Bold(self, b: bool):
        if self.__check_bool(b):
            self.__Bold = b

    def get_Italic(self) -> bool:
        return self.__Italic

    def set_Italic(self, b: bool):
        if self.__check_bool(b):
            self.__Italic = b

    def get_Underline(self) -> bool:
        return self.__Underline

    def set_Underline(self, b: bool):
        if self.__check_bool(b):
            self.__Underline = b

    def get_StrikeOut(self) -> bool:
        return self.__StrikeOut

    def set_StrikeOut(self, b: bool):
        if self.__check_bool(b):
            self.__StrikeOut = b

    def get_ScaleX(self) -> int:
        return self.__ScaleX

    def set_ScaleX(self, i: int):
        if self.__check_int(i):
            self.__ScaleX = round(i)

    def get_ScaleY(self) -> int:
        return self.__ScaleY

    def set_ScaleY(self, i: int):
        if self.__check_int(i):
            self.__ScaleY = round(i)

    def get_Spacing(self) -> int:
        return self.__Spacing

    def set_Spacing(self, i: int):
        if self.__check_int(i):
            self.__Spacing = round(i)

    def get_Angle(self) -> float:
        return self.__Angle

    def set_Angle(self, f: float):
        if self.__check_float(f):
            self.__Angle = float(f)

    def get_BorderStyle(self) -> bool:
        return self.__BorderStyle

    def set_BorderStyle(self, b: bool):
        if self.__check_bool(b):
            self.__BorderStyle = b

    def get_Outline(self) -> int:
        return self.__Outline

    def set_Outline(self, i: int):
        if self.__check_int(i):
            self.__Outline = round(i)

    def get_Shadow(self) -> int:
        return self.__Shadow

    def set_Shadow(self, i: int):
        if self.__check_int(i):
            self.__Shadow = round(i)

    def get_Alignment(self) -> int:
        return self.__Alignment

    def set_Alignment(self, i: int):
        if self.__check_align(i):
            self.__Alignment = round(i)

    def get_MarginL(self) -> int:
        return self.__MarginL

    def set_MarginL(self, i: int):
        if self.__check_int(i):
            self.__MarginL = round(i)

    def get_MarginR(self) -> int:
        return self.__MarginR

    def set_MarginR(self, i: int):
        if self.__check_int(i):
            self.__MarginR = round(i)

    def get_MarginV(self) -> int:
        return self.__MarginV

    def set_MarginV(self, i: int):
        if self.__check_int(i):
            self.__MarginV = round(i)

    def get_Encoding(self) -> int:
        return self.__Encoding

    def set_Encoding(self, i: int):
        if self.__check_int(i):
            self.__Encoding = round(i)

    def db(self, b: bool) -> int:
        return -1 if b else 0

    def db2(self, b: bool) -> int:
        return 3 if b else 1

    def dump(self):
        return f"Style: {self.Name},{self.Fontname},{self.__Fontsize},{self.PrimaryColour.dump()},{self.SecondaryColour.dump()},{self.OutlineColour.dump()},{self.BackColour.dump()},{self.db(self.__Bold)},{self.db(self.__Italic)},{self.db(self.__Underline)},{self.db(self.__StrikeOut)},{self.__ScaleX},{self.__ScaleY},{self.__Spacing},{self.__Angle},{self.db2(self.__BorderStyle)},{self.__Outline},{self.__Shadow},{self.__Alignment},{self.__MarginL},{self.__MarginR},{self.__MarginV},{self.__Encoding}"


class ASSScriptEvent():
    "注：时间为ms"
    __Comment: bool = False
    __Layer: int = 0
    __Start: int = 0
    __End: int = 0
    __Style: str = 'Default'
    __Name: str = ''
    __MarginL: int = 0
    __MarginR: int = 0
    __MarginV: int = 0
    __Effect: str = ''
    __Text: str = ''

    def __init__(self, Start: int = None, End: int = None, Text: str = None, Style: str = None, Comment: bool = None, Layer: int = None, Name: str = None, MarginL: int = None, MarginR: int = None, MarginV: int = None, Effect: str = None):
        if self.__check_int(Start):
            self.__Start = round(Start)
        if self.__check_int(End):
            self.__End = round(End)
        if Text is not None:
            self.__Text = Text
        if Style is not None:
            self.__Style = Style
        if self.__check_bool(Comment):
            self.__Comment = Comment
        if self.__check_int(Layer):
            self.__Layer = round(Layer)
        if Name is not None:
            self.__Name = Name
        if self.__check_int(MarginL):
            self.__MarginL = round(MarginL)
        if self.__check_int(MarginR):
            self.__MarginR = round(MarginR)
        if self.__check_int(MarginV):
            self.__MarginV = round(MarginV)
        if Effect is not None:
            self.__Effect = Effect

    def get_Comment(self) -> bool:
        return self.__Comment

    def set_Comment(self, b: bool):
        if self.__check_bool(b):
            self.__Comment = b

    def get_Layer(self) -> int:
        return self.__Layer

    def set_Layer(self, i: int):
        if self.__check_int(i):
            self.__Layer = round(i)

    def get_Start(self) -> int:
        return self.__Start

    def set_Start(self, i: int):
        if self.__check_int(i):
            self.__Start = round(i)

    def get_End(self) -> int:
        return self.__End

    def set_End(self, i: int):
        if self.__check_int(i):
            self.__End = round(i)

    def get_Style(self) -> str:
        return self.__Style

    def set_Style(self, s: str):
        if s is not None:
            self.__Style = s

    def get_Name(self) -> str:
        return self.__Name

    def set_Name(self, s: str):
        if s is not None:
            self.__Name = s

    def get_MarginL(self) -> int:
        return self.__MarginL

    def set_MarginL(self, i: int):
        if self.__check_int(i):
            self.__MarginL = round(i)

    def get_MarginR(self) -> int:
        return self.__MarginR

    def set_MarginR(self, i: int):
        if self.__check_int(i):
            self.__MarginR = round(i)

    def get_MarginV(self) -> int:
        return self.__MarginV

    def set_MarginV(self, i: int):
        if self.__check_int(i):
            self.__MarginV = round(i)

    def get_Effect(self) -> str:
        return self.__Effect

    def set_Effect(self, s: str):
        if s is not None:
            self.__Effect = s

    def get_Text(self) -> str:
        return self.__Text

    def set_Text(self, s: str):
        if s is not None:
            self.__Text = s

    def __check_bool(self, b: bool) -> bool:
        if b is None:
            return False
        elif isinstance(b, bool):
            return True
        else:
            raise ValueError(lan['MUST_BOOL'])

    def __check_int(self, i: int) -> bool:
        "检查是否为大于等于0的整数（浮点数会转换为整数）"
        if i is None:
            return False
        elif isinstance(i, int):
            if i >= 0:
                return True
            else:
                raise ValueError(lan['OUT_OF_RANGE'])
        elif isinstance(i, float):
            i = round(i)
            if i >= 0:
                return True
            else:
                raise ValueError(lan['OUT_OF_RANGE'])
        else:
            raise ValueError(lan['MUST_NUM'])

    def __time_to_str(self, i: int) -> str:
        t = round(i/10)
        ms = t % 100
        s = int(t/100) % 60
        m = int(t/6000) % 60
        h = int(t/360000)
        return "{:0>1d}:{:0>2d}:{:0>2d}.{:0>2d}".format(h, m, s, ms)

    def __db(self) -> str:
        return 'Comment' if self.__Comment else 'Dialogue'

    def __ft(self) -> str:
        s = self.__Text.replace('\r\n', '\\N')
        s = s.replace('\r', '\\N')
        s = s.replace('\n', '\\N')
        return s

    def dump(self) -> str:
        return f"{self.__db()}: {self.__Layer},{self.__time_to_str(self.__Start)},{self.__time_to_str(self.__End)},{self.__Style},{self.__Name},{self.__MarginL},{self.__MarginR},{self.__MarginV},{self.__Effect},{self.__ft()}"


class ASSScript():
    Script_Info: ASSScriptInfo = None
    V4_Styles: List[ASSScriptStyle] = None
    Events: List[ASSScriptEvent] = None

    def __init__(self):
        self.Script_Info = ASSScriptInfo()
        self.V4_Styles = [ASSScriptStyle()]
        self.Events = [ASSScriptEvent(0, 5000)]

    def __dump_style(self) -> str:
        s = """[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n"""
        for i in self.V4_Styles:
            s = f"{s}{i.dump()}\n"
        return s

    def __dump_events(self) -> str:
        s = """[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"""
        for i in self.Events:
            s = f"{s}{i.dump()}\n"
        return s

    def dump(self) -> str:
        s = self.Script_Info.dump()
        s = s + '\n' + self.__dump_style()
        s = s + '\n' + self.__dump_events()
        return s


def parsefromASSHex(s: str) -> ASSScriptColor:
    re = search(
        r'^&H([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$', s, I)
    if re is not None:
        re = re.groups()
        return ASSScriptColor(int(re[3], 16), int(re[2], 16), int(re[1], 16), int(re[0], 16))
    raise ValueError(lan['INVALID_ASS_COLOR'].replace('<value>', str(s)))


def parsefromCSSHex(s: str) -> ASSScriptColor:
    re = search(
        r'^#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})?$', s, I)
    if re is not None:
        re = re.groups()
        r = ASSScriptColor(int(re[0], 16), int(re[1], 16), int(re[2], 16))
        if re[3]:
            r.set_color(alpha=int(re[3], 16))
        return r
    re = search(r'^#([0-9A-F])([0-9A-F])([0-9A-F])([0-9A-F])?$', s, I)
    if re is not None:
        re = re.groups()
        r = ASSScriptColor(
            int(re[0]*2, 16), int(re[1]*2, 16), int(re[2]*2, 16))
        if re[3]:
            r.set_color(alpha=int(re[3]*2, 16))
        return r
    raise ValueError(lan['INVALID_CSS_COLOR'].replace('<value>', str(s)))


lan = None
se = loadset()
if se == -1 or se == -2:
    se = {}
ip = {}
if len(sys.argv) > 1:
    ip = gopt(sys.argv[1:])
lan = getdict('ASSWriter', getlan(se, ip))
